package com.yyfax.account.service.inner.impl;

import com.google.common.collect.Lists;
import com.jaws.core.common.log.Log;
import com.jaws.core.common.util.DateUtil;
import com.yyfax.account.common.enums.OrderStateEnum;
import com.yyfax.account.common.exception.AccountExceptionCode;
import com.yyfax.account.dao.FuiouDayLiquidationDao;
import com.yyfax.account.dao.LiquidationOrderDao;
import com.yyfax.account.domain.FuiouDayLiquidation;
import com.yyfax.account.domain.LiquidationOrder;
import com.yyfax.account.service.inner.FileService;
import com.yyfax.account.vo.DayLiquidationReq;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

@Service
public class FileServiceImpl implements FileService {
	private Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * 清算文件行内容按“|”切割长度
	 */
	private final static int LIQUIDATION_LINE_SPLIT_LENGTH = 7;

	private final static int INSERT_BATCH_SIZE = 1000;
	/**
	 * 本地挂载根路径
	 */
	@Value("${file.local.root.dir:/}")
	private String localRootDir;

	@Resource
	private LiquidationOrderDao liquidationOrderDao;

	@Resource
	private FuiouDayLiquidationDao fuiouDayLiquidationDao;

	@Override
	public LiquidationOrder saveLiquidationOrder(DayLiquidationReq params) {
		final String op = "FileServiceImpl.saveLiquidationOrder";

		// 对账传参为相对路径，需要拼装成绝对路径
		String filePath = localRootDir.concat("/").concat(params.getFilePath());

		LiquidationOrder po = new LiquidationOrder();
		po.setOrderId(params.getOrderId());
		po.setOrderTime(params.getOrderTime());
		po.setFilePath(filePath);
		po.setLiquidationDay(params.getLiquidationDay());
		po.setState(OrderStateEnum.INIT.getValue());

		try {
			liquidationOrderDao.insert(po);
		} catch (DuplicateKeyException e) {
			logger.warn(Log.op(op).msg("保存订单唯一约束冲突").kv("orderId", params.getOrderId()).toString());

			return liquidationOrderDao.selectByPrimaryKey(params.getOrderId());
		}

		return po;
	}

	@Async
	@Override
	public void parseDayLiquidationFile(LiquidationOrder po) {
		final String op = "FileServiceImpl.parseDayLiquidationFile";

		File file = new File(po.getFilePath());
		if (!file.exists()) {
			// 如果文件不存在
			logger.error(Log.op(op).msg("文件解析异常，文件不存在").kv("LiquidationOrder", po).toString());
			return;
		}

		if (!OrderStateEnum.INIT.isValue(po.getState())) {
			// 如果订单状态不是初始化状态，说明订单已经被执行，则不执行
			logger.warn(Log.op(op).msg("订单状态已经变更，不再解析文件").kv("LiquidationOrder", po).toString());
			return;
		}

		int i = liquidationOrderDao.updateStateSelective(po.getOrderId(), OrderStateEnum.DEALING.getValue(), OrderStateEnum.INIT.getValue());
		if (i < 0) {
			logger.warn(Log.op(op).msg("更新订单初始化状态为处理中，没有命中，不再解析文件").kv("LiquidationOrder", po).toString());
			return;
		}

		// 针对对账文件重复录入的情况，每次触发的时候清楚指定清算日期的数据
		fuiouDayLiquidationDao.deleteByLiquidationDay(po.getLiquidationDay());

		String state = OrderStateEnum.FAIL.getValue();
		boolean isExp = false;

		LineIterator lineIterator = null;
		List<FuiouDayLiquidation> list = Lists.newArrayList();

		List<String> exceptionList = Lists.newArrayList();
		List<String> lineList = Lists.newArrayList();

		int index = 0;
		int batchNum = 0;// 批次号
		try {
			long startTime = System.currentTimeMillis();

			lineIterator = FileUtils.lineIterator(file, CharEncoding.UTF_8);
			while (lineIterator.hasNext()) {
				index++;

				String line = lineIterator.nextLine();
				lineList.add(line);

				FuiouDayLiquidation fuiouLiquidationLine = parseFuiouLiquidationLine(line, exceptionList, index);

				if (!exceptionList.isEmpty() || isExp) {
					isExp = true;
					// 如果存在异常数据，则不入库，并终止
					break;
				}

				list.add(fuiouLiquidationLine);

				if (index % INSERT_BATCH_SIZE == 0 || !lineIterator.hasNext()) {
					// 批量保存到数据库,或者是最后一行
					batchNum++;
					int count = insertBatchFuiouDayLiquidation(file, list, lineList, batchNum);
					if (count != list.size()) {
						isExp = true;
						logger.error(
								Log.op(op).msg("插入文件fuiou_day_liquidation记录数不对").kv("filePath", file.getName()).kv("lineList", lineList).toString());
					}

					list.clear();
					lineList.clear();
				}
			}

			logger.info("解析文件{}，总记录数：{}，耗时：{}ms", po.getFilePath(), index, System.currentTimeMillis() - startTime);

			if (!isExp) {
				state = OrderStateEnum.SUCCESS.getValue();
			}

		} catch (IOException e) {
			logger.error(Log.op(op).msg("文件解析异常").kv("filePath", po.getFilePath()).toString(), e);
			state = OrderStateEnum.FAIL.getValue();
		} finally {
			if (!exceptionList.isEmpty()) {
				// 存在非法数据，写入异常
				writeExceptionTxt(exceptionList, file);
			}

			if (lineIterator != null) {
				LineIterator.closeQuietly(lineIterator);
			}

			if (isExp) {
				// 出现数据异常，全部清理，由业务重复发起请求重新入库
				fuiouDayLiquidationDao.deleteByLiquidationDay(po.getLiquidationDay());
			}

			list = null;
			lineList = null;
			exceptionList = null;
		}

		// 更新订单状态
		liquidationOrderDao.updateStateSelective(po.getOrderId(), state, null);

		MDC.clear();
	}

	/**
	 * 写入数据校验异常文件
	 * 
	 * @author buyi
	 * @date 2017-10-31 15:41:27
	 * @since v1.0.0
	 * @param exceptionList
	 * @param file
	 */
	private void writeExceptionTxt(List<String> exceptionList, File file) {
		final String op = "FileServiceImpl.writeExceptionTxt";
		if (exceptionList == null || exceptionList.isEmpty()) {
			return;
		}

		String fileName = file.getName();
		String dateTime = DateUtil.date2Str(new Date(), DateUtil.FORMAT_YYYYMMDDHHMMSS);
		// 异常文件名，格式:fileName_err_data_.txt
		String errPath = file.getParent().concat(File.separator).concat(fileName.substring(0, fileName.lastIndexOf("."))).concat("_err_data_")
				.concat(dateTime).concat(".txt");

		logger.error(Log.op(op).msg("解析对账文件，数据校验异常，写入异常文件").kv("file", fileName).kv("errDataFile", errPath).toString());

		File errFile = new File(errPath);
		try {
			FileUtils.writeLines(errFile, exceptionList);
		} catch (IOException e) {
			logger.error(Log.op(op).msg("异常批次数据写入异常文件异常").kv("filePath", file.getName()).kv("errPath", errPath).kv("exceptionList", exceptionList)
					.toString(), e);
		} finally {
			exceptionList.clear();
		}
	}

	/**
	 * 批量保存清算日终记录
	 * 
	 * @author buyi
	 * @date 2017-10-12 10:49:55
	 * @since v2.0.0
	 * @param file
	 * @param list
	 */
	private int insertBatchFuiouDayLiquidation(File file, List<FuiouDayLiquidation> list, List<String> lineList, int batchNum) {
		final String op = "FileServiceImpl.insertBatchFuiouDayLiquidation";
		try {
			return fuiouDayLiquidationDao.insertBatch(list);
		} catch (Exception e) {
			// 如果出现未知异常，则将该批量信息保存异常文件

			String fileName = file.getName();
			// 异常文件名，格式:fileName_err.txt
			String errFilePrefix = file.getParent().concat(File.separator).concat(fileName.substring(0, fileName.lastIndexOf("."))).concat("_err")
					.concat(String.valueOf(batchNum));
			String errFileSuffix = ".txt";
			String errPath = errFilePrefix.concat(errFileSuffix);
			try {
				File errFile = new File(errPath);
				int i = 1;
				while (errFile.exists()) {
					// 如果文件存在，则文件名数字累加
					errPath = errFilePrefix.concat("(").concat(String.valueOf(i++)).concat(")").concat(errFileSuffix);
					errFile = new File(errPath);
				}

				FileUtils.writeLines(errFile, lineList);
			} catch (IOException e1) {
				logger.error(
						Log.op(op).msg("异常批次数据写入异常文件异常").kv("filePath", file.getName()).kv("errPath", errPath).kv("lineList", lineList).toString(),
						e1);
			}

			logger.error(Log.op(op).msg("批量保存出现异常，需要人工解决,并将异常批次数据写入异常文件").kv("filePath", file.getName()).kv("errPath", errPath).toString(), e);
		}

		return -1;
	}

	/**
	 * 解析清算日终文件内容为实体对象
	 * 
	 * @author buyi
	 * @date 2017-10-11 15:35:59
	 * @since v2.0.0
	 * @param line
	 * @param exceptionList 异常集合记录
	 * @return
	 */
	private FuiouDayLiquidation parseFuiouLiquidationLine(String line, List<String> exceptionList, int index) {
		final String op = "FileServiceImpl.parseFuiouLiquidationLine";
		if (StringUtils.isBlank(line)) {
			return null;
		}

		/*
		 * 文件内容格式：商户号|手机号|账面余额|可用余额|冻结余额|未转结余额|日期
		 * 日期格式：yyyyMMdd
		 */
		String[] split = line.split("\\|", -1);
		if (split.length != LIQUIDATION_LINE_SPLIT_LENGTH) {
			logger.info(Log.op(op).msg("解析清算文件内容，切割长度异常").kv("line", line).toString());
			exceptionList.add(line.concat("#").concat(index + "").concat("|").concat("文件内容长度异常"));
			return null;
		}

		try {
			FuiouDayLiquidation dayLiquidation = new FuiouDayLiquidation();
			dayLiquidation.setDepositAccountNo(split[1].trim()); // 存管号
			dayLiquidation.setCarryingAmount(Long.valueOf(split[2].trim())); // 账面金额
			dayLiquidation.setAvailAmount(Long.valueOf(split[3].trim())); // 可用金额
			dayLiquidation.setFrozenAmount(Long.valueOf(split[4].trim())); // 冻结金额
			dayLiquidation.setUntransferFrozenAmount(Long.valueOf(split[5].trim())); // 未转冻结金额
			
			//判断日期格式
			String date = split[6].trim();
			boolean matches = Pattern.compile("([0-9]{4}[0-9]{2}[0-9]{1,2})").matcher(date).matches();
			if(!matches){
				throw AccountExceptionCode.SYS_ERROR.expMsg("清算日期格式非法yyyyMMdd");
			}
			
			dayLiquidation.setLiquidationDay(DateUtil.date2Str(DateUtil.str2Date(date, "yyyyMMdd"))); // 清算日期

			return dayLiquidation;
		} catch (Exception e) {
			logger.warn(Log.op(op).msg("解析清算文件内容，切割长度异常").kv("line", line).toString(), e);
			exceptionList.add(line.concat("#").concat(index + "").concat("|").concat("文件解析异常，存在非法参数"));
		}

		return null;
	}
	
	public static void main(String[] args) {
		String date = "2017-11-18";
		boolean matches = Pattern.compile("([0-9]{4}[0-9]{2}[0-9]{1,2})").matcher(date).matches();
		System.out.println(matches);
	}
}
